package com.chinamsp.dydb.mybatis.interceptor;

import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;

import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.util.StringUtils;

import com.chinamsp.dydb.DydbException;
import com.chinamsp.dydb.convert.ORMNameConverter;

import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.util.TablesNamesFinder;

/**
 * 检查 分表操作
 * @author shifenghu
 * @version 1.0
 */
@Intercepts({
	@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
	@Signature(type = Executor.class, method = "insert", args = {MappedStatement.class, Object.class}),
	@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
    @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class})
})
public class ShardingCheckedInterceptor implements Interceptor {
	
	
	private ORMNameConverter ormNameConverter;
	
	public ShardingCheckedInterceptor(ORMNameConverter ormNameConverter){
		this.ormNameConverter = ormNameConverter;
	}
	
	@Override
	public Object intercept(Invocation invocation) throws Throwable {
		checkTables(getSQL(invocation));
		return invocation.proceed();
	}
	
	/**
	 * 获取 sql 语句
	 * @param invocation
	 * @return
	 */
	private String getSQL(Invocation invocation){
		MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
		Object parameter = invocation.getArgs()[1];
		BoundSql boundSql = mappedStatement.getBoundSql(parameter);
		if (boundSql == null || StringUtils.isEmpty(boundSql.getSql())) {
			return null;
		}
		return boundSql.getSql();
	}

	@Override
	public Object plugin(Object target) {
		return Plugin.wrap(target, this);
	}

	@Override
	public void setProperties(Properties properties) {
		
	}
	
	/**
	 * 检查查询的表是否是同一个库，同一个库的标准是表明的前缀相同
	 * @param sql
	 */
	private void checkTables(String sql){
		// 如果没有sql语句，则直接忽略
		if (StringUtils.isEmpty(sql)) {
			return;
		}
		Statement statement;
		try {
			statement = CCJSqlParserUtil.parse(sql);
			Select selectStatement = (Select) statement;
			TablesNamesFinder tablesNamesFinder = new TablesNamesFinder();
			List<String> tableList = tablesNamesFinder.getTableList(selectStatement);
			Set<String> modules = new HashSet<String>(tableList.size());
			for (String table : tableList) {
				modules.add(ormNameConverter.parseModuleName(table));
				if (modules.size() > 1) {
					throw new DydbException(String.format("[%s]查询语句存在跨库查询，涉及的库 %s", sql, tableList.toString()));
				}
			}
		} catch (JSQLParserException e) {
			throw new DydbException(e);
		}
	}
}
